<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/wang-cheng/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/wang-cheng/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/wang-cheng/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/wang-cheng/images/logo.svg" color="#222">

<link rel="stylesheet" href="/wang-cheng/css/main.css">


<link rel="stylesheet" href="/wang-cheng/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"cheng-wang123.gitee.io","root":"/wang-cheng/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="mybatis的优缺点优点：  基于SQL语句编程，相当灵活，不会对应用程序或者数据库的现有设计造成影响，SQL写在xml里，解除了sql与程序代码的耦合，便于统一管理，提供xml标签，支持编写动态SQL语句，并可重用。  与JDBC相比，减少了50%以上的代码量，消除了JDBC大量冗余的代码，不需要手动开关连接  很好的与各种数据库兼容(因为mybatis使用JDBC来连接数据库，所以只要JDB">
<meta property="og:type" content="article">
<meta property="og:title" content="面试题(六)">
<meta property="og:url" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/index.html">
<meta property="og:site_name" content="诚の博客">
<meta property="og:description" content="mybatis的优缺点优点：  基于SQL语句编程，相当灵活，不会对应用程序或者数据库的现有设计造成影响，SQL写在xml里，解除了sql与程序代码的耦合，便于统一管理，提供xml标签，支持编写动态SQL语句，并可重用。  与JDBC相比，减少了50%以上的代码量，消除了JDBC大量冗余的代码，不需要手动开关连接  很好的与各种数据库兼容(因为mybatis使用JDBC来连接数据库，所以只要JDB">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E6%A0%91.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/B%E6%A0%91.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/B+%E6%A0%91.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/MVCC.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/EXPLAIN.png">
<meta property="article:published_time" content="2022-03-23T02:04:22.000Z">
<meta property="article:modified_time" content="2022-05-14T10:58:05.878Z">
<meta property="article:author" content="王诚">
<meta property="article:tag" content="面试题">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E6%A0%91.png">

<link rel="canonical" href="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>面试题(六) | 诚の博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/wang-cheng/atom.xml" title="诚の博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/wang-cheng/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">诚の博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录学习中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/wang-cheng/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/wang-cheng/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/wang-cheng/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/wang-cheng/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          面试题(六)
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-03-23 10:04:22" itemprop="dateCreated datePublished" datetime="2022-03-23T10:04:22+08:00">2022-03-23</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-14 18:58:05" itemprop="dateModified" datetime="2022-05-14T18:58:05+08:00">2022-05-14</time>
              </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h2 id="mybatis的优缺点"><a href="#mybatis的优缺点" class="headerlink" title="mybatis的优缺点"></a><strong>mybatis的优缺点</strong></h2><p>优点：</p>
<ol>
<li><p>基于SQL语句编程，相当灵活，不会对应用程序或者数据库的现有设计造成影响，SQL写在xml里，解除了sql与程序代码的耦合，便于统一管理，提供xml标签，支持编写动态SQL语句，并可重用。</p>
</li>
<li><p>与JDBC相比，减少了50%以上的代码量，消除了JDBC大量冗余的代码，不需要手动开关连接</p>
</li>
<li><p>很好的与各种数据库兼容(因为mybatis使用JDBC来连接数据库，所以只要JDBC支持的数据库mybatis都支持)</p>
</li>
<li><p>能够与Spring很好的集成</p>
</li>
<li><p>提供映射标签，支持对象与数据库的ORM字段关系映射；提供对象关系映射标签，支持对象关系组件维护</p>
</li>
</ol>
<p>缺点：</p>
<ol>
<li><p>SQL语句的编写工作量大，尤其当字段多、关联表多时，对开发人员编写SQL语句的功底有一定要求</p>
</li>
<li><p>SQL语句依赖数据库，导致数据库移植性差，不能随意更换数据库</p>
</li>
</ol>
<h2 id="MyBatis与Hibernate对比"><a href="#MyBatis与Hibernate对比" class="headerlink" title="MyBatis与Hibernate对比"></a><strong>MyBatis与Hibernate对比</strong></h2><p>MyBatis主要是一个通过操作SQL来操作数据库的框架</p>
<p>Hibernate是一个ORM框架，可以通过操作类来操作数据库</p>
<p>开发速度对比：</p>
<p>Hibernate的真正掌握要比mybatis难一些。MyBatis框架相对简单容易上手</p>
<p>比起两者的开发速度，不仅仅要考虑两者的特性及性能，更要根据项目需求去考虑究竟哪一个更适合项目开发</p>
<p>比如：一个项目中用到的复杂查询基本没有，就简单的增删改查，这样选择Hibernate好一些，因为基本的SQL语句已经被封装好了，根本不需要你去写sql语句，这就节省了大量时间，但是对于一个大型项目，复杂语句较多，这样再去选择Hibernate就不是一个太好的选择，选择mybatis就会快很多，而且语句的管理比较方便</p>
<p>开发工作量对比：</p>
<p>Hibernate和MyBatis都有相应的代码生成工具。可以生成简单基本的DAO层方法。针对高级查询，MyBatis需要手动编写SQL语句，以及ResultMap。而Hibernate有良好的映射机制，开发者无需关心SQL的生成与结果的映射，可以更专注于业务流程。</p>
<p>sql优化方面；</p>
<p>Hibernate的查询将表中的所有字段查询出来，这一点会有性能消耗。Hibernate也可以自己写SQL来指定需要查询的字段，但这样就破坏了Hibernate开发的简洁性。而MyBatis的SQL是手动编写的，可以按需求指定查询的字段。</p>
<p>对象管理的对比：</p>
<p>Hibernate是完整的对象/关系映射解决方案，它提供了对象状态管理的功能，使开发者不再需要理会底层数据库系统的细节。也就是说，相对于常见的JDBC/SQL持久层方案中需要管理SQL语句，Hibernate采用了更自然的面向对象的视角来持久化Java应用中的数据</p>
<p>换句话说，使用Hibernate的开发者应该总是关注对象的状态(state)，不必考虑SQL语句的执行。这部分细节已经由Hibernate掌管妥当，只有开发者在进行系统性能调优的时候才需要进行了解。而MyBatis在这一块没有文档说明，用户需要对对象自己进行详细的管理(因为MyBatis不是ORM框架，没有对象管理这个概念)</p>
<p>缓存机制对比：</p>
<p>相同点：都可以实现自己的缓存或使用其他第三方缓存方案，创建适配器来完成覆盖缓存行为</p>
<p>不同点：Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置，然后再在具体的表-对象映射中配置是哪种缓存。MyBatis的二级缓存配置都是在每一个具体的表-对象映射中进行详细配置，这样针对不同的表可以自定义不同的缓存机制。并且MyBatis可以在命名空间中共享相同的缓存配置和实例，通过Cache-ref实现</p>
<p>两者比较：</p>
<p>因为Hibernate对查询对象有着良好的管理，用户无需关心SQL。所以在使用二级缓存时如果出现脏数据，系统会报错误并提示</p>
<p>而MyBatis在这方面，使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围，避免Cache的盲目使用。否则，脏数据的出现会给系统的正常运行带来很大的隐患。</p>
<p>Hibernate功能强大，数据库无关性好，O/R映射能力强，如果你对Hibernate相当精通，并且对Hibernate进行了适当的封装，那么你的项目整个持久层代码会相当简单，需要写的代码很少，开发速度很快</p>
<p>Hibernate的缺点就是学习门槛不低，要精通门槛更高，而且怎么设计O/R映射，在性能和对象模型之间如何权衡取得平衡，以及怎样用好Hibernate方面需要你的经验和能力都很强</p>
<h2 id="和-的区别是什么"><a href="#和-的区别是什么" class="headerlink" title="#{}和${}的区别是什么"></a><strong>#{}和${}的区别是什么</strong></h2><p><code>#&#123;&#125;</code>是预编译处理、是占位符，<code>$&#123;&#125;</code>是字符串替换、是拼接符</p>
<p>MyBatis在处理<code>#&#123;&#125;</code>时，会将sql中的<code>#&#123;&#125;</code>替换为？号，调用PreparedStatement来赋值</p>
<p>MyBatis在处理<code>$&#123;&#125;</code>时，就是把<code>$&#123;&#125;</code>替换为变量的值，调用Statement来赋值</p>
<p><code>#&#123;&#125;</code>的变量替换是在DBMS中、变量替换后，<code>#&#123;&#125;</code>对应的变量自动加上单引号</p>
<p><code>$&#123;&#125;</code>的变量替换是在DBMS外、变量替换后，<code>$&#123;&#125;</code>对应的变量不会加上单引号</p>
<p>使用<code>#&#123;&#125;</code>可以有效的防止SQL注入，提高系统安全性</p>
<h2 id="statement-和-preparedstatement区别"><a href="#statement-和-preparedstatement区别" class="headerlink" title="statement 和 preparedstatement区别"></a><strong>statement 和 preparedstatement区别</strong></h2><p>首先是preparedStatement<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">public void add(Config config)&#123;</span><br><span class="line">    String sql = <span class="string">&quot;insert into category values(null,?,?)&quot;</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Connection c = <span class="module-access"><span class="module"><span class="identifier">DBUtil</span>.</span></span>get<span class="constructor">Connection()</span>;</span><br><span class="line">        PreparedStatement ps = c.prepare<span class="constructor">Statement(<span class="params">sql</span>)</span>;</span><br><span class="line">        ps.set<span class="constructor">String(1, <span class="params">config</span>.<span class="params">key</span>)</span>;</span><br><span class="line">        ps.set<span class="constructor">String(2, <span class="params">config</span>.<span class="params">value</span>)</span>;</span><br><span class="line">        ps.execute<span class="literal">()</span>;</span><br><span class="line">        ResultSet rs = ps.get<span class="constructor">GeneratedKeys()</span>;</span><br><span class="line">        <span class="keyword">while</span> (rs.next<span class="literal">()</span>) &#123;</span><br><span class="line">            <span class="built_in">int</span> id = rs.get<span class="constructor">Int(1)</span>;</span><br><span class="line">            config.id = id;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; catch (SQLException e) &#123;</span><br><span class="line">        e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>statement<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">( <span class="type">int</span> id)</span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">Connection</span> <span class="variable">c</span> <span class="operator">=</span> DBUtil.getConnection();</span><br><span class="line">        <span class="type">Statement</span> <span class="variable">s</span> <span class="operator">=</span> c.createStatement();</span><br><span class="line">        <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;delete from category where id = &quot;</span> + id ;</span><br><span class="line">        s.execute(sql);</span><br><span class="line">    &#125;<span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<ol>
<li><p>概念上</p>
<ul>
<li><p>prepareStatement会先初始化SQL，先把这个SQL提交到数据库中进行预处理，多次使用可提高效率。</p>
</li>
<li><p>createStatement不会初始化，没有预处理，每次都是从0开始执行SQL。</p>
</li>
</ul>
</li>
<li><p>变量上</p>
<ul>
<li><p>prepareStatement可以在SQL中用?替换变量；</p>
</li>
<li><p>createStatement不支持 ? 替换变量，只能在sql中拼接参数；</p>
</li>
</ul>
</li>
<li><p>功能上：如果想要删除三条数据</p>
<ul>
<li>对于createStatement，需要写三条语句  <figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">String sql = &quot;<span class="keyword">delete</span> <span class="keyword">from</span> category <span class="keyword">where</span> id = <span class="number">2</span><span class="string">&quot;  ; </span></span><br><span class="line"><span class="string">String sql = &quot;</span><span class="keyword">delete</span> <span class="keyword">from</span> category <span class="keyword">where</span> id = <span class="number">3</span><span class="string">&quot;  ; </span></span><br><span class="line"><span class="string">String sql = &quot;</span><span class="keyword">delete</span> <span class="keyword">from</span> category <span class="keyword">where</span> id = <span class="number">7</span><span class="string">&quot;  ;</span></span><br></pre></td></tr></table></figure></li>
<li><p>而prepareStatement，通过set不同数据只需要生成一次执行计划，可以重用</p>
  <figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String sql <span class="operator">=</span> <span class="string">&quot;delete from category where id = ？&quot;</span>  <span class="comment">;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>PreparedStatement是预编译的,对于批量处理可以大大提高效率.也叫JDBC存储过程。</p>
</li>
<li><p>使用createStatement 对象。在对数据库只执行一次性存取的时侯，用 createStatement对象进行处理。PreparedStatement对象的开销比createStatement大，对于一次性操作并不会带来额外的好处。</p>
</li>
<li><p>createStatement每次执行sql语句，相关数据库都要执行sql语句的编译，preparedstatement是预编译得,preparedstatement支持批处理</p>
</li>
</ul>
</li>
</ol>
<h2 id="乐观锁以及乐观锁的实现"><a href="#乐观锁以及乐观锁的实现" class="headerlink" title="乐观锁以及乐观锁的实现"></a><strong>乐观锁以及乐观锁的实现</strong></h2><p>乐观锁假设认为数据一般情况下不会造成冲突，所以在数据进行提交更新的时候，才会正式对数据的冲突与否进行检测，如果发现冲突了，则让返回用户错误的信息，让用户决定如何去做</p>
<p>乐观锁的实现：</p>
<p>使用数据版本（Version）记录机制实现，这是乐观锁最常用的一种实现方式。何谓数据版本？即为数据增加一个版本标识，一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时，将version字段的值一同读出，数据每更新一次，对此version值加一。当我们提交更新的时候，判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对，如果数据库表当前版本号与第一次取出来的version值相等，则予以更新，否则认为是过期数据</p>
<p>1、数据库表设计</p>
<p>task</p>
<p>有三个字段，分别是id,value、version</p>
<p>2.实现</p>
<p>1）先读task表的数据（实际上这个表只有一条记录），得到version的值为versionValue</p>
<p>2)每次更新task表中的value字段时，为了防止发生冲突，需要这样操作</p>
<p>update task set value = newValue,version =  versionValue + 1   where version = versionValue;</p>
<p>只有这条语句执行了，才表明本次更新value字段的值成功</p>
<p>如假设有两个节点A和B都要更新task表中的value字段值，差不多在同一时刻，A节点和B节点从task表中读到的version值为2，那么A节点和B节点在更新value字段值的时候，都操作 update task set value = newValue,version =  3   where version = 2;,实际上只有1个节点执行该SQL语句成功，假设A节点执行成功，那么此时task表的version字段的值是3，B节点再操作update task set value = newValue,version =  3   where version = 2;这条SQL语句是不执行的，这样就保证了更新task表时不发生冲突</p>
<p>项目中使用案例：<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 基于乐观锁的更新操作</span></span><br><span class="line"><span class="comment">    * @param editFinance 编辑的账户对象</span></span><br><span class="line"><span class="comment">    * @param queryLockNo 上次查询的乐观锁版本号</span></span><br><span class="line"><span class="comment">    * @return</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">@Override</span><br><span class="line">public <span class="built_in">int</span> update<span class="constructor">ForLockNo(BzFinanceEntity <span class="params">editFinance</span>, <span class="params">int</span> <span class="params">queryLockNo</span>)</span> &#123;</span><br><span class="line"></span><br><span class="line">    editFinance.set<span class="constructor">LockNo(<span class="params">queryLockNo</span> + 1)</span>; <span class="comment">//修改乐观锁版本</span></span><br><span class="line"></span><br><span class="line">    BzFinanceEntityExample example = <span class="keyword">new</span> <span class="constructor">BzFinanceEntityExample()</span>;</span><br><span class="line">    BzFinanceEntityExample.Criteria criteria = example.create<span class="constructor">Criteria()</span>;</span><br><span class="line">    criteria.<span class="keyword">and</span><span class="constructor">IdFinanceEqualTo(<span class="params">editFinance</span>.<span class="params">getIdFinance</span>()</span>);</span><br><span class="line">    criteria.<span class="keyword">and</span><span class="constructor">LockNoEqualTo(<span class="params">queryLockNo</span>)</span>; <span class="comment">//基于乐观锁，修改查询版本的数据</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//根据Example条件更新实体BzFinanceEntity包含的不是null的属性值</span></span><br><span class="line">    <span class="built_in">int</span> mark = this.baseEntityDao.update<span class="constructor">ByExampleSelective(<span class="params">editFinance</span>, <span class="params">example</span>)</span>;</span><br><span class="line">    return mark;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="哈夫曼编码"><a href="#哈夫曼编码" class="headerlink" title="哈夫曼编码"></a><strong>哈夫曼编码</strong></h2><p>哈夫曼编码(Huffman Coding)，又称霍夫曼编码，是一种编码方式，可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法，该方法完全依据字符出现概率来构造异字头的平均长度最短的码字，有时称之为最佳编码，一般就叫做Huffman编码（有时也称为霍夫曼编码）。</p>
<p>哈夫曼编码，主要目的是根据使用频率来最大化节省字符（编码）的存储空间。</p>
<p>霍夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩，加密解密等场合。</p>
<p>如果考虑到进一步节省存储空间，就应该将出现概率大（占比多）的字符用尽量少的0-1进行编码，也就是更靠近根（节点少），这也就是最优二叉树-哈夫曼树。</p>
<p>权值大的在上层，权值小的在下层。满足出现频率高的码长短，即带权路径最小的树, 权值最小的结点远离根结点, 权值越大的结点越靠近根结点</p>
<p>哈夫曼编码的带权路径权值：叶子节点的值 * 叶子节点的高度（根节点为0）</p>
<h2 id="哈夫曼编码结构"><a href="#哈夫曼编码结构" class="headerlink" title="哈夫曼编码结构"></a><strong>哈夫曼编码结构</strong></h2><p>例如要发送一段字符串BCAADDDCCACACAC</p>
<p>由于每个字符占8个比特，上面有15个字符，共占120比特。</p>
<p>现在我们统计每一种字符出现的频率：</p>
<p>A:5</p>
<p>B:1</p>
<p>C:6</p>
<p>D:3</p>
<p>将他们排序后就是B1、D3、A5、C6</p>
<p>然后我们现在创建一个空节点z，将频率最低的字符串放在z左侧、频率第二低的字符串放在z右侧，z赋值为两个字符频率的和。</p>
<p>重复上面的操作，直到所有的字符串都放到二叉树中。</p>
<p>对每一个非叶子节点，将0分配给连接线的左侧，1分配给连接线的右侧。</p>
<p>此时霍夫曼树就构建完成了。</p>
<img src="/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E6%A0%91.png" class title="霍夫曼树">
<p>此时A对应的编码为11，B对应编码为100，C对应编码为0，D对应编码为101</p>
<p>而如果不适用霍夫曼编码，字符串的二进制为<br>10000100100001101000001010000010100010001000100010001000100001101000011010000010100001101000001010000110100000101000011</p>
<p>编码后：0000001001011011011111111111</p>
<h2 id="简述mybatis的插件运行原理，如何编写一个插件"><a href="#简述mybatis的插件运行原理，如何编写一个插件" class="headerlink" title="简述mybatis的插件运行原理，如何编写一个插件"></a><strong>简述mybatis的插件运行原理，如何编写一个插件</strong></h2><p>Mybatis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口的插件，mybatis使用JDK的动态代理，为需要拦截的接口生成代理对象以实现接口方法拦截功能，每当执行这4种接口对象的方法时，就会进行拦截方法，具体就是InvocationHandler的invoke()方法，拦截那些你指定需要拦截的方法</p>
<h2 id="B-树和B树的区别在哪里-蚂蚁金服"><a href="#B-树和B树的区别在哪里-蚂蚁金服" class="headerlink" title="B+树和B树的区别在哪里(蚂蚁金服)"></a><strong>B+树和B树的区别在哪里(蚂蚁金服)</strong></h2><p>因为B+树非叶子节点是不存储数据的，B树是存储数据的，而且B+树的叶子结点上有所有的数据，而B树的数据是散落在树的各个层级里面，且，B+树的子节点还有指针相连，是一个双向链表、这样，虽然B树的节点数比B+树少，但是应付一些全表扫描的场景上，B+树更有优势；并且，在范围查询的场景中，我们找到了一个值，B+树会更方便顺序取出剩下的所有值。</p>
<p><strong>B树有如下特性：</strong></p>
<p>是一种多路搜索树（并不是二叉的）：</p>
<ol>
<li>定义任意非叶子结点最多只有M个儿子，且M&gt;2；</li>
<li>根结点的儿子数为[2, M]；</li>
<li>除根结点以外的非叶子结点的儿子数为[M/2, M]；</li>
<li>每个结点存放至少M/2-1（取上整）和至多M-1个关键字；（至少2个关键字）</li>
<li>非叶子结点的关键字个数=指向儿子的指针个数-1；</li>
<li>非叶子结点的关键字：K[1], K[2], …, K[M-1]，且K[i] &lt; K[i+1]；</li>
<li>非叶子结点的指针：P[1], P[2], …, P[M]，其中P[1]指向关键字小于K[1]的子树，P[M]指向关键字大于K[M-1]的子树，其它P[i]指向关键字属于(K[i-1], K[i])的子树；</li>
<li>所有叶子结点位于同一层；</li>
</ol>
<img src="/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/B%E6%A0%91.png" class title="B树">
<p>这样就降低了树的高度，也就降低了磁盘的IO次数。但是很遗憾，数据库采用的还不是B树，而是B+树。</p>
<p><strong>B+树的特性：</strong></p>
<p>B+树是B-树的变体，也是一种多路搜索树：</p>
<ol>
<li>其定义基本与B-树同，除了：</li>
<li>非叶子结点的子树指针与关键字个数相同；</li>
<li>非叶子结点的子树指针P[i]，指向关键字值属于[K[i], K[i+1])的子树</li>
<li>B-树是开区间；</li>
<li>为所有叶子结点增加一个链指针；</li>
<li>所有关键字都在叶子结点出现</li>
</ol>
<img src="/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/B+%E6%A0%91.png" class title="B+树">
<p>mysql就是使用这种B+树来存储数据的，所有的数据都在叶子节点，方便做全文检索，且，每次IO的次数都是一样的，比较稳定。</p>
<h2 id="索引的基本原理"><a href="#索引的基本原理" class="headerlink" title="索引的基本原理"></a><strong>索引的基本原理</strong></h2><p>索引用来快速地寻找那些具有特定值的记录。如果没有索引，一般来说执行查询时遍历整张表。<br>索引的原理：就是把无序的数据变成有序的查询</p>
<ol>
<li>把创建了索引的列的内容进行排序</li>
<li>对排序结果生成倒排表</li>
<li>在倒排表内容上拼上数据地址链</li>
<li>在查询的时候，先拿到倒排表内容，再取出数据地址链，从而拿到具体数据</li>
</ol>
<h2 id="mysql聚簇和非聚簇索引的区别"><a href="#mysql聚簇和非聚簇索引的区别" class="headerlink" title="mysql聚簇和非聚簇索引的区别"></a><strong>mysql聚簇和非聚簇索引的区别</strong></h2><p>都是B+树的数据结构</p>
<ul>
<li><p>聚簇索引：将数据存储与索引放到了一块、并且是按照一定的顺序组织的，找到索引也就找到了数据，数据的物理存放顺序与索引顺序是一致的，即：只要索引是相邻的，那么对应的数据一定也是相邻地存放在磁盘上的</p>
</li>
<li><p>非聚簇索引：叶子节点不存储数据、存储的是数据行地址，也就是说根据索引查找到数据行的位置再取磁盘查找数据，这个就有点类似一本树的目录，比如我们要找第三章第一节，那我们先在这个目录里面找，找到对应的页码后再去对应的页码看文章。</p>
</li>
</ul>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">优势：</span><br><span class="line"><span class="number">1</span>、查询通过聚簇索引可以直接获取数据，相比非聚簇索引需要第二次查询（非覆盖索引的情况下）效率要高</span><br><span class="line"><span class="number">2</span>、聚簇索引对于范围查询的效率很高，因为其数据是按照大小排列的 </span><br><span class="line"><span class="number">3</span>、聚簇索引适合用在排序的场合，非聚簇索引不适合 </span><br><span class="line">劣势： </span><br><span class="line"><span class="number">1</span>、维护索引很昂贵，特别是插入新行或者主键被更新导致要分页(page split)的时候。建议在大量插入新行后，选在负载较低的时间段，通过OPTIMIZE <span class="keyword">TABLE</span>优化表，因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片 </span><br><span class="line"><span class="number">2</span>、表因为使用<span class="type">UUId</span>（随机ID）作为主键，使数据存储稀疏，这就会出现聚簇索引有可能有比全表扫面 更慢，所以建议使用<span class="type">int</span>的auto_increment作为主键 </span><br><span class="line"><span class="number">3</span>、如果主键比较大的话，那辅助索引将会变的更大，因为辅助索引的叶子存储的是主键值；过长的主键值，会导致非叶子节点占用占用更多的物理空间</span><br></pre></td></tr></table></figure>
<p>InnoDB中一定有主键，主键一定是聚簇索引，不手动设置、则会使用unique索引，没有unique索引，则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引，辅助索引访问数据总是需要二次查找，非聚簇索引都是辅助索引，像复合索引、前缀索引、唯一索引，辅助索引叶子节点存储的不再是行的物理位置，而是主键值</p>
<p>MyISM使用的是非聚簇索引，没有聚簇索引，非聚簇索引的两棵B+树看上去没什么不同，节点的结构完全一致只是存储的内容不同而已，主键索引B+树的节点存储了主键，辅助键索引B+树存储了辅助键。表数据存储在独立的地方，这两颗B+树的叶子节点都使用一个地址指向真正的表数据，对于表数据来说，这两个键没有任何差别。由于索引树是独立的，通过辅助键检索无需访问主键的索引树。</p>
<p>如果涉及到大数据量的排序、全表扫描、count之类的操作的话，还是MyISAM占优势些，因为索引所占空间小，这些操作是需要在内存中完成的。</p>
<h2 id="索引设计的原则？"><a href="#索引设计的原则？" class="headerlink" title="索引设计的原则？"></a><strong>索引设计的原则？</strong></h2><p>查询更快、占用空间更小</p>
<ol>
<li>适合索引的列是出现在where子句中的列，或者连接子句中指定的列</li>
<li>基数较小的表，索引效果较差，没有必要在此列建立索引</li>
<li>使用短索引，如果对长字符串列进行索引，应该指定一个前缀长度，这样能够节省大量索引空间，<br>如果搜索词超过索引前缀长度，则使用索引排除不匹配的行，然后检查其余行是否可能匹配。</li>
<li>不要过度索引。索引需要额外的磁盘空间，并降低写操作的性能。在修改表内容的时候，索引会进<br>行更新甚至重构，索引列越多，这个时间就会越长。所以只保持需要的索引有利于查询即可。</li>
<li>定义有外键的数据列一定要建立索引。</li>
<li>更新频繁字段不适合创建索引</li>
<li>若是不能有效区分数据的列不适合做索引列(如性别，男女未知，最多也就三种，区分度实在太低)</li>
<li>尽量的扩展索引，不要新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可。</li>
<li>对于那些查询中很少涉及的列，重复值比较多的列不要建立索引。</li>
<li>对于定义为text、image和bit的数据类型的列不要建立索引。</li>
</ol>
<h2 id="什么是最左前缀原则？什么是最左匹配原则"><a href="#什么是最左前缀原则？什么是最左匹配原则" class="headerlink" title="什么是最左前缀原则？什么是最左匹配原则"></a><strong>什么是最左前缀原则？什么是最左匹配原则</strong></h2><p>在有多索引的数据库中，查询数据时要按照顺序依次查询索引，中间如果跳过某个索引，就不是按照索引进行查找的。</p>
<p>按照索引查找能提高查询速度</p>
<h2 id="锁的类型有哪些"><a href="#锁的类型有哪些" class="headerlink" title="锁的类型有哪些"></a><strong>锁的类型有哪些</strong></h2><p>基于锁的属性分类：共享锁、排他锁。</p>
<p>基于锁的粒度分类：行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎 )、记录锁、间隙锁、临键锁。</p>
<p>基于锁的状态分类：意向共享锁、意向排它锁</p>
<ul>
<li><p>共享锁(Share Lock)</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">共享锁又称读锁，简称S锁；当一个事务为数据加上读锁之后，其他事务只能对该数据加读锁，而不能对 数据加写锁，直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持 并发的读取数据，读取数据的时候不支持修改，避免出现重复读的问题。</span><br></pre></td></tr></table></figure>
</li>
<li><p>排他锁（Exclusive Lock）</p>
  <figure class="highlight tp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">排他锁又称写锁，简称<span class="keyword">X</span>锁；当一个事务为数据加上写锁时，其他请求将不能再为数据加任何锁，直到该锁释放之后，其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候，不允许其他人同时修改，也不允许其他人读取。避免了出现脏数据和脏读的问题</span><br></pre></td></tr></table></figure>
</li>
<li><p>表锁</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">表锁是指上锁的时候锁住的是整个表，当下一个事务访问该表的时候，必须等前一个事务释放了锁才能进行对表进行访问； 特点： 粒度大，加锁简单，容易冲突；</span><br></pre></td></tr></table></figure>
</li>
<li><p>行锁</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">行锁是指上锁的时候锁住的是表的某一行或多行记录，其他事务访问同一张表时，只有被锁住的记录不能访问，其他的记录可正常访问； 特点：粒度小，加锁比表锁麻烦，不容易冲突，相比表锁支持的并发要高；</span><br></pre></td></tr></table></figure>
</li>
<li><p>记录锁(Record Lock)</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">记录锁也属于行锁中的一种，只不过记录锁的范围只是表中的某一条记录，记录锁是说事务在加锁后锁住的只是表的某一条记录。 精准条件命中，并且命中的条件字段是唯一索引,加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题，也避免了在修改的事务未提交前被其他事务读取的脏读问题。</span><br></pre></td></tr></table></figure>
</li>
<li><p>页锁</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快，但冲突多，行级冲突少，但速度慢。所以取了折衷的页级，一次锁定相邻的一组记录。特点：开销和加锁时间界于表锁和行锁之间；会出现死锁；锁定粒度界于表锁和行锁之间，并发度一般</span><br></pre></td></tr></table></figure>
</li>
<li><p>间隙锁(Gap Lock)</p>
  <figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">属于行锁中的一种，间隙锁是在事务加锁后其锁住的是表记录的某一个区间，当表的相邻ID之间出现空隙则会形成一个区间，遵循左开右闭原则。范围查询并且查询未命中记录，查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ（重复读)的事务级别中。 触发条件：防止幻读问题，事务并发的时候，如果没有间隙锁，就会发生如下图的问题，在同一个事务里，A事务的两次查询出的结果会不一样。比如表里面的数据ID 为 <span class="number">1</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">7</span>,<span class="number">10</span> ,那么会形成以下几个间隙区间，-<span class="built_in">n</span> - <span class="number">1</span>区间，<span class="number">1</span>-<span class="number">4</span>区间，<span class="number">7</span>-<span class="number">10</span> 区间，<span class="number">10</span>-<span class="built_in">n</span>区间 （-<span class="built_in">n</span>代表负无穷大，<span class="built_in">n</span>代表正无穷大）</span><br></pre></td></tr></table></figure>
</li>
<li><p>临建锁(Next-Key Lock)</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">也属于行锁的一种，并且它是INNODB的行锁默认算法，总结来说它就是记录锁和间隙锁的组合，临键锁会把查询出来的记录锁住，同时也会把该范围查询内的所有间隙空间也会锁住，再之它会把相邻的下一个区间也会锁住。触发条件：范围查询并命中，查询命中了索引。 结合记录锁和间隙锁的特性，临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之 后，在范围区间内数据不允许被修改和插入</span><br></pre></td></tr></table></figure>
<p>如果当事务A加锁成功之后就设置一个状态告诉后面的人，已经有人对表里的行加了一个排他锁<br>了，你们不能对整个表加共享锁或排它锁了，那么后面需要对整个表加锁的人只需要获取这个状态<br>就知道自己是不是可以对表加锁，避免了对整个索引树的每个节点扫描是否加锁，而这个状态就是<br>意向锁。</p>
</li>
<li><p>意向共享锁</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当一个事务试图对整个表进行加共享锁之前，首先需要获得这个表的意向共享锁。</span><br></pre></td></tr></table></figure></li>
<li><p>意向排他锁</p>
  <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当一个事务试图对整个表进行加排它锁之前，首先需要获得这个表的意向排它锁。</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="InnoDB存储引擎的锁的算法"><a href="#InnoDB存储引擎的锁的算法" class="headerlink" title="InnoDB存储引擎的锁的算法"></a><strong>InnoDB存储引擎的锁的算法</strong></h2><ul>
<li><p>Record lock：单个行记录上的锁</p>
</li>
<li><p>Gap lock：间隙锁，锁定一个范围，不包括记录本身</p>
</li>
<li><p>Next-key lock：record+gap 锁定一个范围，包含记录本身</p>
</li>
</ul>
<p>相关知识点：</p>
<ol>
<li><p>innodb对于行的查询使用next-key lock</p>
</li>
<li><p>Next-locking keying为了解决Phantom Problem幻读问题</p>
</li>
<li><p>当查询的索引含有唯一属性时，将next-key lock降级为record key</p>
</li>
<li><p>Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内，而这会导致幻读问题的产生</p>
</li>
<li><p>有两种方式显式关闭gap锁：（除了外键约束和唯一性检查外，其余情况仅使用record lock） </p>
</li>
</ol>
<p>A.将事务隔离级别设置为RC </p>
<p>B. 将参数innodb_locks_unsafe_for_binlog设置为1</p>
<h2 id="关心过业务系统里面的sql耗时吗？统计过慢查询吗？对查询都怎么优化过？"><a href="#关心过业务系统里面的sql耗时吗？统计过慢查询吗？对查询都怎么优化过？" class="headerlink" title="关心过业务系统里面的sql耗时吗？统计过慢查询吗？对查询都怎么优化过？"></a><strong>关心过业务系统里面的sql耗时吗？统计过慢查询吗？对查询都怎么优化过？</strong></h2><p>在业务系统中，除了使用主键进行的查询，其他的都会在测试库上测试其耗时，慢查询的统计主要由运维在做，会定期将业务中的慢查询反馈给我们。</p>
<p>慢查询的优化首先要搞明白慢的原因是什么？是查询条件没有命中索引？是load了不需要的数据列？还是数据量太大？</p>
<p>所以优化也是针对这三个方向来的，</p>
<ul>
<li><p>首先分析语句，看看是否load了额外的数据，可能是查询了多余的行并且抛弃掉了，可能是加载了许多结果中并不需要的列，对语句进行分析以及重写。</p>
</li>
<li><p>分析语句的执行计划，然后获得其使用索引的情况，之后修改语句或者修改索引，使得语句可以尽可能的命中索引。</p>
</li>
<li><p>如果对语句的优化已经无法进行，可以考虑表中的数据量是否太大，如果是的话可以进行横向或者纵向的分表。</p>
</li>
</ul>
<h2 id="ACID靠什么保证的？"><a href="#ACID靠什么保证的？" class="headerlink" title="ACID靠什么保证的？"></a><strong>ACID靠什么保证的？</strong></h2><p>A原子性由undo log日志保证，它记录了需要回滚的日志信息，事务回滚时撤销已经执行成功的sql</p>
<p>C一致性由其他三大特性保证、程序代码要保证业务上的一致性</p>
<p>I隔离性由MVCC来保证</p>
<p>D持久性由内存+redo log来保证，mysql修改数据同时在内存和redo log记录这次操作，宕机的时候可以从redo log恢复</p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">InnoDB redo <span class="keyword">log</span> 写盘，InnoDB 事务进入 <span class="keyword">prepare</span> 状态。 如果前面 <span class="keyword">prepare</span> 成功，binlog 写盘，再继续将事务日志持久化到 binlog，如果持久化成功，那么 InnoDB 事务则进入 <span class="keyword">commit</span> 状态(在 redo <span class="keyword">log</span> 里面写一个 <span class="keyword">commit</span> 记录)</span><br></pre></td></tr></table></figure>
<p>redolog的刷盘会在系统空闲时进行</p>
<h2 id="ACID"><a href="#ACID" class="headerlink" title="ACID"></a><strong>ACID</strong></h2><p>A 原子性 要么都执行，要么都不执行 </p>
<p>C 一致性 事物执行前后数据库保持一致的正确状态 </p>
<p>I 隔离性 不同事务之间互不干扰 </p>
<p>D 持久性 事务一旦提交，对数据库中数据的变更是永久的</p>
<h2 id="什么是MVCC"><a href="#什么是MVCC" class="headerlink" title="什么是MVCC"></a><strong>什么是MVCC</strong></h2><p><strong>事务的隔离级别的实现。</strong></p>
<p>多版本并发控制：读取数据时通过一种类似快照的方式将数据保存下来，这样读锁就和写锁不冲突了，不同的事务session会看到自己特定版本的数据，版本链。</p>
<p>MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁</p>
<p>聚簇索引记录中有两个必要的隐藏列：</p>
<p><strong>trx_id</strong>：用来存储每次对某条聚簇索引记录进行修改的时候的事务id。</p>
<p><strong>roll_pointer</strong>：每次对哪条聚簇索引记录有修改的时候，都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针，它指向这条聚簇索引记录的上一个版本的位置，通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性，因为它没有老版本)</p>
<p><strong>已提交读和可重复读的区别就在于它们生成ReadView的策略不同。</strong></p>
<img src="/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/MVCC.png" class title="MVCC">
<p>开始事务时创建readview，readView维护当前活动的事务id，即未提交的事务id，排序生成一个数组访问数据，获取数据中的事务id（获取的是事务id最大的记录），对比readview：</p>
<p>如果在readview的左边（比readview都小），可以访问（在左边意味着该事务已经提交）</p>
<p>如果在readview的右边（比readview都大）或者就在readview中，不可以访问，获取roll_pointer，取上一版本重新对比（在右边意味着，该事务在readview生成之后出现，在readview中意味着该事务还未提交）</p>
<p>已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView，之后的读都复用之前的ReadView。</p>
<p>这就是Mysql的MVCC,通过版本链，实现多版本，可并发读-写，写-读。通过ReadView生成策略的不同实现不同的隔离级别。</p>
<p><strong>快照读是基于 MVCC 和 undo log 来实现的，适用于简单 select 语句</strong></p>
<p><strong>当前读是基于 临键锁（行锁 + 间歇锁）来实现的</strong></p>
<h2 id="mysql主从同步原理"><a href="#mysql主从同步原理" class="headerlink" title="mysql主从同步原理"></a><strong>mysql主从同步原理</strong></h2><p>mysql主从同步的过程：</p>
<p>Mysql的主从复制中主要有三个线程： <code>master（binlog dump thread）</code>、<code>slave（I/O thread</code> 、<code>SQL thread）</code> ，Master一条线程和Slave中的两条线程。</p>
<ul>
<li><p>主节点 binlog，主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服务器启动的那一刻起，保存所有修改数据库结构或内容的一个文件。</p>
</li>
<li><p>主节点 log dump 线程，当 binlog 有变动时，log dump 线程读取其内容并发送给从节点。从节点 I/O线程接收 binlog 内容，并将其写入到 relay log 文件中。</p>
</li>
<li><p>从节点的SQL 线程读取 relay log 文件内容对数据更新进行重放，最终保证主从数据库的一致性</p>
</li>
</ul>
<p>注：主从节点使用 binglog 文件 + position 偏移量来定位主从同步的位置，从节点会保存其已接收到的偏移量，如果从节点发生宕机重启，则会自动从 position 的位置发起同步。(增量同步)</p>
<p>由于mysql默认的复制方式是异步的，主库把日志发送给从库后不关心从库是否已经处理，这样会产生一个问题就是假设主库挂了，从库处理失败了，这时候从库升为主库后，日志就丢失了。由此产生两个概念。</p>
<p><strong>全同步复制</strong></p>
<p>主库写入binlog后强制同步日志到从库，所有的从库都执行完成后才返回给客户端，但是很显然这个方式的话性能会受到严重影响。</p>
<p><strong>半同步复制</strong></p>
<p>和全同步不同的是，半同步复制的逻辑是这样，从库写入日志成功后返回ACK确认给主库，主库收到至少一个从库的确认就认为写操作完成。</p>
<h2 id="简述MyISAM和InnoDB的区别"><a href="#简述MyISAM和InnoDB的区别" class="headerlink" title="简述MyISAM和InnoDB的区别"></a><strong>简述MyISAM和InnoDB的区别</strong></h2><p><strong>MyISAM</strong>：</p>
<p>不支持事务，但是每次查询都是原子的；</p>
<p>支持表级锁，即每次操作是对整个表加锁；</p>
<p>存储表的总行数；</p>
<p>一个MYISAM表有三个文件：索引文件、表结构文件、数据文件；</p>
<p>采用非聚集索引，索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致，但是辅索引不用保证唯一性。</p>
<p>InnoDb：</p>
<p>支持ACID的事务，支持事务的四种隔离级别；</p>
<p>支持行级锁及外键约束：因此可以支持写并发；</p>
<p>不存储总行数；</p>
<p>一个InnoDb引擎存储在一个文件空间（共享表空间，表大小不受操作系统控制，一个表可能分布在多个文件里），也有可能为多个（设置为独立表空，表大小受操作系统文件大小限制，一般为2G），受操作系统文件大小的限制；</p>
<p>主键索引采用聚集索引（索引的数据域存储数据文件本身），辅索引的数据域存储主键的值；因此从辅索引查找数据，需要先通过辅索引找到主键值，再访问辅索引；最好使用自增主键，防止插入数据时，为维持B+树结构，文件的大调整。</p>
<h2 id="简述mysql中索引类型及对数据库的性能的影响"><a href="#简述mysql中索引类型及对数据库的性能的影响" class="headerlink" title="简述mysql中索引类型及对数据库的性能的影响"></a><strong>简述mysql中索引类型及对数据库的性能的影响</strong></h2><p>普通索引：允许被索引的数据列包含重复的值。</p>
<p>唯一索引：可以保证数据记录的唯一性。</p>
<p>主键：是一种特殊的唯一索引，在一张表中只能定义一个主键索引，主键用于唯一标识一条记录，使用关键字 PRIMARY KEY 来创建。</p>
<p>联合索引：索引可以覆盖多个数据列，如像INDEX(columnA, columnB)索引</p>
<p>全文索引：通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题，是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引</p>
<p>索引可以极大的提高数据的查询速度。</p>
<p>通过使用索引，可以在查询的过程中，使用优化隐藏器，提高系统的性能。</p>
<p>但是会降低插入、删除、更新表的速度，因为在执行这些写操作时，还要操作索引文件索引需要占物理空间，除了数据表占数据空间之外，每一个索引还要占一定的物理空间，如果要建立聚簇索引，那么需要的空间就会更大，如果非聚集索引很多，一旦聚集索引改变，那么所有非聚集索引都会跟着变。</p>
<h2 id="mysql执行计划怎么看"><a href="#mysql执行计划怎么看" class="headerlink" title="mysql执行计划怎么看"></a><strong>mysql执行计划怎么看</strong></h2><p>执行计划就是sql的执行查询的顺序，以及如何使用索引查询，返回的结果集的行数</p>
<p>EXPLAIN SELECT * from A where X=? and Y=?</p>
<img src="/wang-cheng/2022/03/23/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AD/EXPLAIN.png" class title="EXPLAIN">
<ol>
<li><p>id ：是一个有顺序的编号，是查询的顺序号，有几个 select 就显示几行。id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行，id列的值相同则从上往下执行，id列的值为NULL最后执行。</p>
</li>
<li><p>selectType 表示查询中每个select子句的类型</p>
<ul>
<li><p>SIMPLE： 表示此查询不包含 UNION 查询或子查询</p>
</li>
<li><p>PRIMARY： 表示此查询是最外层的查询（包含子查询）</p>
</li>
<li><p>SUBQUERY： 子查询中的第一个 SELECT</p>
</li>
<li><p>UNION： 表示此查询是 UNION 的第二或随后的查询</p>
</li>
<li><p>DEPENDENT UNION： UNION 中的第二个或后面的查询语句, 取决于外面的查询</p>
</li>
<li><p>UNION RESULT, UNION 的结果</p>
</li>
<li><p>DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.</p>
</li>
<li><p>DERIVED：衍生，表示导出表的SELECT（FROM子句的子查询）</p>
</li>
</ul>
</li>
<li><p>table：表示该语句查询的表</p>
</li>
<li><p>type：优化sql的重要字段，也是我们判断sql性能和优化程度重要指标。他的取值类型范围：</p>
<ul>
<li><p>const：通过索引一次命中，匹配一行数据</p>
</li>
<li><p>system: 表中只有一行记录，相当于系统表；</p>
</li>
<li><p>eq_ref：唯一性索引扫描，对于每个索引键，表中只有一条记录与之匹配</p>
</li>
<li><p>ref: 非唯一性索引扫描,返回匹配某个值的所有</p>
</li>
<li><p>range: 只检索给定范围的行，使用一个索引来选择行，一般用于between、&lt;、&gt;；</p>
</li>
<li><p>index: 只遍历索引树；</p>
</li>
<li><p>ALL: 表示全表扫描，这个类型的查询是性能最差的查询之一。 那么基本就是随着表的数量增多，执行效率越慢。</p>
<p>  <strong>执行效率：</strong><br>  <strong>ALL &lt; index &lt; range&lt; ref &lt; eq_ref &lt; const &lt; system。最好是避免ALL和index</strong></p>
</li>
</ul>
</li>
<li><p>possible_keys：它表示Mysql在执行该sql语句的时候，可能用到的索引信息，仅仅是可能，实际不一定会用到。</p>
</li>
<li><p>key：此字段是 mysql 在当前查询时所真正使用到的索引。 他是possible_keys的子集</p>
</li>
<li><p>key_len：表示查询优化器使用了索引的字节数，这个字段可以评估组合索引是否完全被使用，这也是我们优化sql时，评估索引的重要指标</p>
</li>
<li><p>rows：mysql 查询优化器根据统计信息，估算该sql返回结果集需要扫描读取的行数，这个值相关重要，索引优化之后，扫描读取的行数越多，说明索引设置不对，或者字段传入的类型之类的问题，说明要优化空间越大</p>
</li>
<li><p>filtered：返回结果的行占需要读到的行(rows列的值)的百分比，就是百分比越高，说明需要查询到数据越准确， 百分比越小，说明查询到的数据量大，而结果集很少</p>
</li>
<li><p>extra</p>
<ul>
<li><p>using filesort ：表示 mysql 对结果集进行外部排序，不能通过索引顺序达到排序效果。一般有using filesort都建议优化去掉，因为这样的查询 cpu 资源消耗大，延时大。</p>
</li>
<li><p>using index：覆盖索引扫描，表示查询在索引树中就可查找所需数据，不用扫描表数据文件，往往说明性能不错。</p>
</li>
<li><p>using temporary：查询有使用临时表, 一般出现于排序， 分组和多表 join 的情况， 查询效率不高，建议优化。</p>
</li>
<li><p>using where ：sql使用了where过滤,效率较高。</p>
</li>
</ul>
</li>
</ol>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/wang-cheng/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/" rel="tag"># 面试题</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/03/22/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%BA%94/" rel="prev" title="面试题(五)">
      <i class="fa fa-chevron-left"></i> 面试题(五)
    </a></div>
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/03/23/JVM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" rel="next" title="JVM学习笔记">
      JVM学习笔记 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#mybatis%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9"><span class="nav-number">1.</span> <span class="nav-text">mybatis的优缺点</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#MyBatis%E4%B8%8EHibernate%E5%AF%B9%E6%AF%94"><span class="nav-number">2.</span> <span class="nav-text">MyBatis与Hibernate对比</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%92%8C-%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88"><span class="nav-number">3.</span> <span class="nav-text">#{}和${}的区别是什么</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#statement-%E5%92%8C-preparedstatement%E5%8C%BA%E5%88%AB"><span class="nav-number">4.</span> <span class="nav-text">statement 和 preparedstatement区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B9%90%E8%A7%82%E9%94%81%E4%BB%A5%E5%8F%8A%E4%B9%90%E8%A7%82%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="nav-number">5.</span> <span class="nav-text">乐观锁以及乐观锁的实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%93%88%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81"><span class="nav-number">6.</span> <span class="nav-text">哈夫曼编码</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%93%88%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81%E7%BB%93%E6%9E%84"><span class="nav-number">7.</span> <span class="nav-text">哈夫曼编码结构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%80%E8%BF%B0mybatis%E7%9A%84%E6%8F%92%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%EF%BC%8C%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E6%8F%92%E4%BB%B6"><span class="nav-number">8.</span> <span class="nav-text">简述mybatis的插件运行原理，如何编写一个插件</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#B-%E6%A0%91%E5%92%8CB%E6%A0%91%E7%9A%84%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%E9%87%8C-%E8%9A%82%E8%9A%81%E9%87%91%E6%9C%8D"><span class="nav-number">9.</span> <span class="nav-text">B+树和B树的区别在哪里(蚂蚁金服)</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86"><span class="nav-number">10.</span> <span class="nav-text">索引的基本原理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#mysql%E8%81%9A%E7%B0%87%E5%92%8C%E9%9D%9E%E8%81%9A%E7%B0%87%E7%B4%A2%E5%BC%95%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="nav-number">11.</span> <span class="nav-text">mysql聚簇和非聚簇索引的区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E8%AE%BE%E8%AE%A1%E7%9A%84%E5%8E%9F%E5%88%99%EF%BC%9F"><span class="nav-number">12.</span> <span class="nav-text">索引设计的原则？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%80%E5%B7%A6%E5%89%8D%E7%BC%80%E5%8E%9F%E5%88%99%EF%BC%9F%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%80%E5%B7%A6%E5%8C%B9%E9%85%8D%E5%8E%9F%E5%88%99"><span class="nav-number">13.</span> <span class="nav-text">什么是最左前缀原则？什么是最左匹配原则</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%94%81%E7%9A%84%E7%B1%BB%E5%9E%8B%E6%9C%89%E5%93%AA%E4%BA%9B"><span class="nav-number">14.</span> <span class="nav-text">锁的类型有哪些</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#InnoDB%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E%E7%9A%84%E9%94%81%E7%9A%84%E7%AE%97%E6%B3%95"><span class="nav-number">15.</span> <span class="nav-text">InnoDB存储引擎的锁的算法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%B3%E5%BF%83%E8%BF%87%E4%B8%9A%E5%8A%A1%E7%B3%BB%E7%BB%9F%E9%87%8C%E9%9D%A2%E7%9A%84sql%E8%80%97%E6%97%B6%E5%90%97%EF%BC%9F%E7%BB%9F%E8%AE%A1%E8%BF%87%E6%85%A2%E6%9F%A5%E8%AF%A2%E5%90%97%EF%BC%9F%E5%AF%B9%E6%9F%A5%E8%AF%A2%E9%83%BD%E6%80%8E%E4%B9%88%E4%BC%98%E5%8C%96%E8%BF%87%EF%BC%9F"><span class="nav-number">16.</span> <span class="nav-text">关心过业务系统里面的sql耗时吗？统计过慢查询吗？对查询都怎么优化过？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#ACID%E9%9D%A0%E4%BB%80%E4%B9%88%E4%BF%9D%E8%AF%81%E7%9A%84%EF%BC%9F"><span class="nav-number">17.</span> <span class="nav-text">ACID靠什么保证的？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#ACID"><span class="nav-number">18.</span> <span class="nav-text">ACID</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFMVCC"><span class="nav-number">19.</span> <span class="nav-text">什么是MVCC</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#mysql%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5%E5%8E%9F%E7%90%86"><span class="nav-number">20.</span> <span class="nav-text">mysql主从同步原理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%80%E8%BF%B0MyISAM%E5%92%8CInnoDB%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="nav-number">21.</span> <span class="nav-text">简述MyISAM和InnoDB的区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%80%E8%BF%B0mysql%E4%B8%AD%E7%B4%A2%E5%BC%95%E7%B1%BB%E5%9E%8B%E5%8F%8A%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E6%80%A7%E8%83%BD%E7%9A%84%E5%BD%B1%E5%93%8D"><span class="nav-number">22.</span> <span class="nav-text">简述mysql中索引类型及对数据库的性能的影响</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#mysql%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92%E6%80%8E%E4%B9%88%E7%9C%8B"><span class="nav-number">23.</span> <span class="nav-text">mysql执行计划怎么看</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">王诚</p>
  <div class="site-description" itemprop="description">命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/wang-cheng/archives/">
        
          <span class="site-state-item-count">32</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/wang-cheng/tags/">
          
        <span class="site-state-item-count">12</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">王诚</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/wang-cheng/lib/anime.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.ui.min.js"></script>

<script src="/wang-cheng/js/utils.js"></script>

<script src="/wang-cheng/js/motion.js"></script>


<script src="/wang-cheng/js/schemes/pisces.js"></script>


<script src="/wang-cheng/js/next-boot.js"></script>




  















  

  

  

</body>
</html>
